package com.example.bootasync.service.impl;

import cn.hutool.json.JSON;
import cn.hutool.json.JSONObject;
import com.example.bootasync.conf.RedisCache;
import com.example.bootasync.core.enums.SeckillKeys;
import com.example.bootasync.entity.Prod;
import com.example.bootasync.entity.bo.OrderMsgBo;
import com.example.bootasync.entity.bo.ProdMsgBo;
import com.example.bootasync.service.IProdService;
import com.example.bootasync.service.SeckillService;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author : JinMing Zhou
 * @description: 秒杀类
 * @date : 2023/6/2 9:49
 */
@Service
public class SeckillServiceImpl implements SeckillService {

    @Resource
    private RedisCache redisCache;

    @Resource
    private IProdService prodService;

    @Resource
    private RabbitTemplate rabbitTemplate;


    /**
     * 开启秒杀
     * @param startTime 秒杀开始时间
     * @param endTime 秒杀结束时间
     * @param prodIds 开启的商品id
     * @return 是否成功
     */
    @Override
    public void openSeckill(LocalDateTime startTime, LocalDateTime endTime, List<Long> prodIds) {
        if (endTime.compareTo(startTime) >= 0){
            throw new RuntimeException("结束时间不能小于等于开始时间");
        }

        // 没有开始秒杀之前就需要可以查询

        // 存储开始时间
        redisCache.setCacheObject(SeckillKeys.START_TIME.getKeyName(), startTime);

        // 存储商品信息
        long minutes = ChronoUnit.SECONDS.between(LocalDateTime.now(), endTime);
        // 商品map，key为id，value是详情
        Map<String, Prod> prods = prodService.listProdByIds(prodIds)
                .stream()
                .collect(
                        Collectors.toMap(
                                item -> item.getId().toString(), // 这里工具类中需要转为String
                                item -> item,
                                (item1, item2) -> item1
                        )
                );
        redisCache.setCacheMap(SeckillKeys.PROD_INFOS.getKeyName(), prods);
        // 指定存储时间到结束时间
        redisCache.expire(SeckillKeys.PROD_INFOS.getKeyName(), minutes, TimeUnit.SECONDS);
    }

    /**
     * 获取秒杀商品详情
     * @return 商品集合
     */
    @Override
    public List<Prod> listSeckillProds() {
        return redisCache.getCacheList(SeckillKeys.PROD_INFOS.getKeyName());
    }

    /**
     * 去redis中获取商品详情
     * @param prodId 商品id
     * @return 商品详情
     */
    @Override
    public Prod getProdInfo(String prodId) {
        Prod prod = redisCache.getCacheMapValue(SeckillKeys.PROD_INFOS.getKeyName(), prodId);
        if (Objects.isNull(prod)){
            redisCache.setCacheMapValue(SeckillKeys.PROD_INFOS.getKeyName(), prodId, prodService.getInfo(Long.valueOf(prodId)));// 或者再次存到redis中
        }
        return prod;
    }

    /**
     * 购买商品
     * @param userId 用户id
     * @param prodId 商品id
     * @return 是否成功
     */
    @Override
    public Boolean buyProd(Long userId, Long prodId) {
        Prod prod = redisCache.getCacheMapValue(SeckillKeys.HAS_ORDER.getKeyName(), userId.toString());
        String key = SeckillKeys.HAS_ORDER.getKeyName() + userId + "_" + prodId;
        if (redisCache.hasKey(key)) {
            throw new RuntimeException("一个商品只能购买一次");
        }
        // 验证库存
        Prod prodInfo = this.getProdInfo(prodId.toString());
        Integer num = prodInfo.getNum();
        if (prodInfo.getNum() <= 0){
            throw new RuntimeException("商品太火爆了，看看其他的吧");
        }
        // 减库存
        prodInfo.setNum(--num);
        redisCache.setCacheMapValue(SeckillKeys.PROD_INFOS.getKeyName(), prodId.toString(), prodInfo);
        // 这里可以走mq
        // rabbitMQ修改MySQL库存
        rabbitTemplate.convertSendAndReceive("", "update_prod", new JSONObject(new ProdMsgBo(prodId, prodInfo.getNum())));

        // 存订单
        redisCache.setCacheObject(key, 1);
        // rabbitMQ异步订单
        rabbitTemplate.convertSendAndReceive("", "create_order", new JSONObject(new OrderMsgBo(userId, prodId, LocalDateTime.now())));
        return true;
    }

}
